/**
 * 
 */
package pl.net.beercrm.stats;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import pl.net.beercrm.model.Analysis;
import pl.net.beercrm.model.Customer;
import pl.net.beercrm.model.Ingredient;
import pl.net.beercrm.model.Product;
import pl.net.beercrm.model.Purchase;
import pl.net.beercrm.service.GenericService;
import pl.net.beercrm.service.ProductService;
import pl.net.beercrm.service.PurchaseService;

/**
 * @author Rafi
 * 
 */
@Component
public class AnalysisProvider {

	private static final Log log = LogFactory.getLog(AnalysisProvider.class);

	@Autowired
	private GenericService<Customer, Long> customerService;

	@Autowired
	private ProductService<Product, Long> productService;

	@Autowired
	private PurchaseService<Purchase, Long> purchaseService;

	@Autowired
	private GenericService<Analysis, Long> analysisService;

	@Autowired
	private DiscountChecker discountChecker;

	public Analysis prepareAnalysisSet(String customerId) {
		Analysis analysis = new Analysis();
		analysis.setCustomer(customerId);
		analysis.setDate(new Date());
		analysis.setFavoriteBrand(getFavoriteBrand(customerId));
		analysis.setPriceMatch(getPriceMatch(customerId));
		analysis.setRegionMatch(getRegionMatch(customerId));
		analysis.setIngredientMatch(getIngredientMatch(customerId));
		Integer globalDiscount = discountChecker
				.checkGlobalDiscount(customerId);
		Date discountExpiry = discountChecker.checkDiscountDuration(customerId);
		analysis.setDiscount(globalDiscount);
		analysis.setDiscountExpiry(discountExpiry);
		if (discountExpiry.after(new Date())) {
			analysis.setDiscountStart(new Date());
		}

		List<Analysis> persistedObjects = analysisService
				.findByGuid(customerId);
		if (persistedObjects.size() > 0) {
			Analysis persistedAnalysis = persistedObjects.get(0);
			if (persistedAnalysis.equals(analysis)) {
				log.debug("No changes in analysis for customer " + customerId);
				return null;
			}
		}

		analysisService.upsert(analysis);
		return analysis;
	}

	public String getFavoriteBrand(String customerId) {
		String favoriteBrand = null;
		Integer maxAmount = 0;
		Map<String, Integer> purchases = getPurchaseMap(customerId);
		for (Entry<String, Integer> entry : purchases.entrySet()) {
			if (entry.getValue() > maxAmount) {
				maxAmount = entry.getValue();
				favoriteBrand = entry.getKey();
			}
		}
		log.debug("[Favorite Brand] Customer " + customerId
				+ " favorite brand: " + favoriteBrand);
		return favoriteBrand;
	}

	public String getPriceMatch(String customerId) {
		String priceMatch = null;
		Set<String> purchasedProducts = getPurchaseMap(customerId).keySet();
		double averagePrice = getWeightedAveragePrice(customerId);
		double minPriceDiff = Double.MAX_VALUE;
		for (Product product : productService.list()) {
			double priceDiff = Math.abs(product.getPrice().doubleValue()
					- averagePrice);
			if ((priceDiff < minPriceDiff)
					&& !purchasedProducts.contains(product.getProductId())) {
				minPriceDiff = priceDiff;
				priceMatch = product.getProductId();
			}
		}
		log.debug("[Price Match] Customer " + customerId + " price match: "
				+ priceMatch + ". Price diff: " + minPriceDiff);
		return priceMatch;
	}

	public String getRegionMatch(String customerId) {
		String regionMatch = null;
		Integer maxAmount = 0;
		Map<String, Integer> regionMap = getRegionPurchaseMap(customerId);
		for (Entry<String, Integer> entry : regionMap.entrySet()) {
			if (entry.getValue() > maxAmount) {
				maxAmount = entry.getValue();
				regionMatch = entry.getKey();
			}
		}
		log.debug("[Region Match] Customer " + customerId + " region match: "
				+ regionMatch);
		return regionMatch;
	}

	public String getIngredientMatch(String customerId) {
		String ingredientMatch = null;
		Map<String, Double> ingredientMap = getIngredientMatchMap(customerId);
		Double minDiff = Double.MAX_VALUE;
		for (Entry<String, Double> entry : ingredientMap.entrySet()) {
			if (entry.getValue() < minDiff) {
				minDiff = entry.getValue();
				ingredientMatch = entry.getKey();
			}
		}
		log.debug("[Ingredient Match] Customer " + customerId
				+ " ingredient match: " + ingredientMatch);
		return ingredientMatch;
	}

	private Map<String, Double> getIngredientMatchMap(String customerId) {
		Map<String, Double> ingredientMap = new HashMap<String, Double>(0);
		String favoriteProduct = getFavoriteBrand(customerId);
		List<Product> products = productService.findByGuid(favoriteProduct);
		if (products.size() > 0) {
			Product persistedProduct = products.get(0);
			Set<Ingredient> referenceIngredients = productService
					.getIngredients(persistedProduct);
			List<Product> anotherProducts = productService.list();
			for (Product product : anotherProducts) {
				if (product.getProductId().equalsIgnoreCase(favoriteProduct))
					continue;
				Set<Ingredient> ingredients = productService
						.getIngredients(product);
				double diff = 0;
				for (Ingredient ingr : referenceIngredients) {
					for (Ingredient i : ingredients) {
						if (i.getIngredientId().equalsIgnoreCase(
								ingr.getIngredientId())) {
							diff += Math.abs(i.getAmount() - ingr.getAmount());
						}
					}
				}
				ingredientMap.put(product.getProductId(), diff);
			}
		}
		return ingredientMap;
	}

	private Map<String, Integer> getRegionPurchaseMap(String customerId) {
		Map<String, Integer> regionPurchase = new HashMap<String, Integer>(0);
		List<Customer> persistedObjects = customerService
				.findByGuid(customerId);
		if (persistedObjects.size() > 0) {
			Customer referenceCustomer = persistedObjects.get(0);
			List<Customer> customersByRegion = customerService.findByProperty(
					"state", referenceCustomer.getState());
			for (Customer customer : customersByRegion) {
				// Process all customers except that customer for whom analysis
				// is prepared
				if (customer.getCustomerId().equalsIgnoreCase(customerId))
					continue;
				Map<String, Integer> tempMap = getPurchaseMap(customer
						.getCustomerId());
				for (Entry<String, Integer> entry : tempMap.entrySet()) {
					if (!regionPurchase.containsKey(entry.getKey())) {
						regionPurchase.put(entry.getKey(), entry.getValue());
					} else {
						regionPurchase.put(entry.getKey(), (regionPurchase
								.get(entry.getKey()) + entry.getValue()));
					}
				}
				log.debug("[Region Match] " + customer.toString());
			}
		}
		return regionPurchase;
	}

	private double getWeightedAveragePrice(String customerId) {
		double averagePrice = 0.0;
		Map<String, Integer> purchaseMap = getPurchaseMap(customerId);
		double totalAmount = 0.0;
		for (Integer i : purchaseMap.values()) {
			totalAmount += i.doubleValue();
		}

		List<Customer> customers = customerService.findByGuid(customerId);
		if (customers.size() > 0) {
			Customer persistedCustomer = customers.get(0);
			List<Purchase> persistedPurchases = purchaseService
					.getPurchaseByCustomer(persistedCustomer);
			for (Purchase purchase : persistedPurchases) {
				Product product = purchaseService.getProduct(purchase);
				double price = product.getPrice().doubleValue();
				double amount = Integer.valueOf(purchase.getAmount())
						.doubleValue();
				double fraction = (amount / totalAmount);

				averagePrice += price * fraction;
			}
		}
		log.debug("[Price Match] Average price for customer " + customerId
				+ ": " + averagePrice);
		return averagePrice;
	}

	private Map<String, Integer> getPurchaseMap(String customerId) {
		Map<String, Integer> map = new HashMap<String, Integer>(0);
		List<Customer> customers = customerService.findByGuid(customerId);
		if (customers.size() > 0) {
			Customer persistedCustomer = customers.get(0);
			List<Purchase> persistedPurchases = purchaseService
					.getPurchaseByCustomer(persistedCustomer);
			for (Purchase purchase : persistedPurchases) {
				Product product = purchaseService.getProduct(purchase);
				String key = product.getProductId();
				Integer value = purchase.getAmount();
				if (map.containsKey(key))
					value += map.get(key);
				map.put(key, value);
			}
		}
		return map;
	}

}
